//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#include "WeXInputDeviceHandler.h"
#include "WeDebug.h"
#include "WeVector2.h"

#include <Windows.h>
#include <Xinput.h>

//TODO: put all these defines in the class and use normalized values
#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE  7849
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD    30
#define XINPUT_VIRTUAL_BUTTON_THRESHOLD		0.92f //TODO: put this in class as member var!

#define XINPUT_GAMEPAD_LEFT_THUMB_NORMALIZE_FACTOR	1.0f / (32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE)
#define XINPUT_GAMEPAD_RIGHT_THUMB_NORMALIZE_FACTOR 1.0f / (32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE)
#define XINPUT_NORMALIZE_TRIGGER_FACTOR				1.0f / (255.0f - XINPUT_GAMEPAD_TRIGGER_THRESHOLD) 

namespace WackiEngine
{


	// raw XINPUT input states for current previous frame only relevant for implementation
	XINPUT_STATE		xInputPrevState[4];
	XINPUT_STATE 		xInputState[4];
	// mapping of XINPUT keys to WackiEngine::KeyCodes 
	Map<WORD, KeyCode>	buttonMap;

	XInputDH::XInputDH()
	{
		for(uint32 i = 0; i < XUSER_MAX_COUNT; ++i)
			_controllers[i].deviceId = i; // TODO: get correct deviceId here

		// map KeyCodes to gamepad buttons
		buttonMap[XINPUT_GAMEPAD_DPAD_UP] = KC_Gamepad_DPadUp;
		buttonMap[XINPUT_GAMEPAD_DPAD_DOWN] = KC_Gamepad_DPadDown;
		buttonMap[XINPUT_GAMEPAD_DPAD_LEFT] = KC_Gamepad_DPadLeft;
		buttonMap[XINPUT_GAMEPAD_DPAD_RIGHT] = KC_Gamepad_DPadRight;
		buttonMap[XINPUT_GAMEPAD_START] = KC_Gamepad_SpecialRight;
		buttonMap[XINPUT_GAMEPAD_BACK] = KC_Gamepad_SpecialLeft;
		buttonMap[XINPUT_GAMEPAD_LEFT_THUMB] = KC_Gamepad_LeftThumbstick;
		buttonMap[XINPUT_GAMEPAD_RIGHT_THUMB] = KC_Gamepad_RightThumbstick;
		buttonMap[XINPUT_GAMEPAD_LEFT_SHOULDER] = KC_Gamepad_LeftShoulder;
		buttonMap[XINPUT_GAMEPAD_RIGHT_SHOULDER] = KC_Gamepad_RightShoulder;
		buttonMap[XINPUT_GAMEPAD_A] = KC_Gamepad_FaceButtonBottom;
		buttonMap[XINPUT_GAMEPAD_B] = KC_Gamepad_FaceButtonRight;
		buttonMap[XINPUT_GAMEPAD_X] = KC_Gamepad_FaceButtonLeft;
		buttonMap[XINPUT_GAMEPAD_Y] = KC_Gamepad_FaceButtonTop;
	}

	void XInputDH::update()
	{
		// iterate over all controllers to check their states
		for(_currentIndex = 0; _currentIndex < XUSER_MAX_COUNT; ++_currentIndex)
		{
			// updateStatus
			checkStatus();

			// did something change this frame?
			if(xInputState[_currentIndex].dwPacketNumber != xInputPrevState[_currentIndex].dwPacketNumber) {
				updateAxes();
				updateVirtualButtons();
				updateButtons();
				generateEvents();

				// update previous button states
				_controllers[_currentIndex].prevButtons = _controllers[_currentIndex].buttons;

				// update previous XINPUT_STATE
				xInputPrevState[_currentIndex] = xInputState[_currentIndex];
			}
		}
	}

	void XInputDH::checkStatus()
	{
		DeviceStatus& controller = _controllers[_currentIndex];
		XINPUT_STATE& state = xInputState[_currentIndex];

		//TODO: fire connected event
		if(XInputGetState(_currentIndex, &state) == ERROR_SUCCESS) {
			if(controller.connected == false)
				LOGINFO("Xbox controller #" + toString(controller.deviceId, 0) + " connected.");

			controller.connected = true;
		}
		else {
			//TODO: fire disconnect event
			if(controller.connected == true)
				LOGINFO("Xbox controller #" + toString(controller.deviceId, 0) + " disconnected.");

			controller.connected = false;
		}
	}

	void XInputDH::updateAxes()
	{
		DeviceStatus& controller = _controllers[_currentIndex];
		XINPUT_STATE& state = xInputState[_currentIndex];

		Vector2f	leftThumbDir(state.Gamepad.sThumbLX, state.Gamepad.sThumbLY);
		float		leftThumbMag = leftThumbDir.length();
		leftThumbDir.normalize();

		Vector2f	rightThumbDir(state.Gamepad.sThumbRX, state.Gamepad.sThumbRY);
		float		rightThumbMag = rightThumbDir.length();
		rightThumbDir.normalize();

		float		leftTriggerMag = state.Gamepad.bLeftTrigger;
		float		rightTriggerMag = state.Gamepad.bRightTrigger;




		//-----------------
		// LEFT STICK
		//-----------------

		//TODO: we currently have the deadzone correction in this class, it would be better if we did this in the input class
		//		and just send raw axis data up the chain

		if(leftThumbMag > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
			// make sure the magnitude is in bounds
			if(leftThumbMag > 32767) leftThumbMag = 32767;
			// correct for deadzone
			leftThumbMag -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
			// normalize magnitude
			leftThumbMag *= XINPUT_GAMEPAD_LEFT_THUMB_NORMALIZE_FACTOR;

			// set the axis values for the thumb sticks
			controller.axes[KC_Gamepad_LeftX] = leftThumbMag * leftThumbDir.x;
			controller.axes[KC_Gamepad_LeftY] = leftThumbMag * leftThumbDir.y;
		}
		else
		{
			controller.axes[KC_Gamepad_LeftX] = 0.0f;
			controller.axes[KC_Gamepad_LeftY] = 0.0f;
		}

		//-----------------
		// RIGHT STICK
		//-----------------

		if(rightThumbMag > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
			// make sure the magnitude is in bounds
			if(rightThumbMag > 32767) rightThumbMag = 32767;
			// correct for deadzone
			rightThumbMag -= XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
			// normalize magnitude
			rightThumbMag *= XINPUT_GAMEPAD_RIGHT_THUMB_NORMALIZE_FACTOR;

			// set the axis values for the thumb sticks
			controller.axes[KC_Gamepad_RightX] = rightThumbMag * rightThumbDir.x;
			controller.axes[KC_Gamepad_RightY] = rightThumbMag * rightThumbDir.y;
		}
		else
		{
			controller.axes[KC_Gamepad_RightX] = 0.0f;
			controller.axes[KC_Gamepad_RightY] = 0.0f;
		}

		//-----------------
		// LEFT TRIGGER
		//-----------------

		if(leftTriggerMag > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) {
			// make sure the magnitude is in bounds
			if(leftTriggerMag > 255) leftTriggerMag = 255;
			// correct for threshold
			leftTriggerMag -= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
			// normalize
			leftTriggerMag *= XINPUT_NORMALIZE_TRIGGER_FACTOR;

			controller.axes[KC_Gamepad_LeftTriggerAxis] = leftTriggerMag;
		}
		else
		{
			controller.axes[KC_Gamepad_LeftTriggerAxis] = 0.0f;
		}

		//-----------------
		// RIGHT TRIGGER
		//-----------------

		if(rightTriggerMag > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) {
			// make sure the magnitude is in bounds
			if(rightTriggerMag > 255) rightTriggerMag = 255;
			// correct for threshold
			rightTriggerMag -= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
			// normalize
			rightTriggerMag *= XINPUT_NORMALIZE_TRIGGER_FACTOR;

			controller.axes[KC_Gamepad_RightTriggerAxis] = rightTriggerMag;
		}
		else
		{
			controller.axes[KC_Gamepad_RightTriggerAxis] = 0.0f;
		}

	}

	void XInputDH::updateVirtualButtons()
	{
		// left stick
		updateVirtualButton(KC_Gamepad_LeftX, KC_Gamepad_LeftStickLeft, KC_Gamepad_LeftStickRight, XINPUT_VIRTUAL_BUTTON_THRESHOLD);
		updateVirtualButton(KC_Gamepad_LeftY, KC_Gamepad_LeftStickDown, KC_Gamepad_LeftStickUp, XINPUT_VIRTUAL_BUTTON_THRESHOLD);

		// right stick
		updateVirtualButton(KC_Gamepad_RightX, KC_Gamepad_RightStickLeft, KC_Gamepad_RightStickRight, XINPUT_VIRTUAL_BUTTON_THRESHOLD);
		updateVirtualButton(KC_Gamepad_RightY, KC_Gamepad_RightStickDown, KC_Gamepad_RightStickUp, XINPUT_VIRTUAL_BUTTON_THRESHOLD);

		// left trigger
		updateVirtualButton(KC_Gamepad_LeftTriggerAxis, KC_Gamepad_LeftTrigger, XINPUT_VIRTUAL_BUTTON_THRESHOLD);

		// right trigger
		updateVirtualButton(KC_Gamepad_RightTriggerAxis, KC_Gamepad_RightTrigger, XINPUT_VIRTUAL_BUTTON_THRESHOLD);
	}


	// updates two virtual buttons based on an axis that has a range from -X to +X where X >= threshold
	void XInputDH::updateVirtualButton(KeyCode axis, KeyCode buttonNegative, KeyCode buttonPositive, float threshold)
	{
		// below threshold
		if(_controllers[_currentIndex].axes[axis] < -threshold) {
			setButton(buttonNegative, true);
			setButton(buttonPositive, false);
		}
		// Above threshold
		else if(_controllers[_currentIndex].axes[axis] > threshold) {
			setButton(buttonNegative, false);
			setButton(buttonPositive, true);
		}		
		// between threshold
		else {
			setButton(buttonNegative, false);
			setButton(buttonPositive, false);
		}
	}

	// updates a virtual button corresponding to an axis value
	void XInputDH::updateVirtualButton(KeyCode axis, KeyCode button, float threshold)
	{
		if(_controllers[_currentIndex].axes[axis] > threshold)
			setButton(button, true);
		else
			setButton(button, false);
	}

	void XInputDH::updateButtons()
	{
		WORD buttonState = xInputState[_currentIndex].Gamepad.wButtons;
		WORD button;
		KeyCode kc;

		for(auto& buttonPair : buttonMap)
		{
			button = buttonPair.first;
			kc = buttonPair.second;

			// set the current button state
			setButton(kc, (button & buttonState) != 0);
		}
	}

	void XInputDH::generateEvents()
	{
		DeviceStatus&	controller = _controllers[_currentIndex];
		KeyCode			kc;
		bool			prevDown;
		bool			down;

		// generate axis events
		for(auto& axis : controller.axes)
		{
			axisMoved(controller.deviceId, axis.second, axis.first);
		}

		// generate button events
		for(auto& button : controller.buttons)
		{
			kc = button.first;
			down = button.second;
			prevDown = getButtonPrev(kc);
			
			// generate button events
			if(down && !prevDown)
				keyDown(controller.deviceId, kc);
			else if(!down && prevDown)
				keyUp(controller.deviceId, kc);
		}
	}

	void XInputDH::setButton(KeyCode button, bool value)
	{
		// set current frame button value
		_controllers[_currentIndex].buttons[button] = value;
	}

	bool XInputDH::getButton(KeyCode button)
	{
		DeviceStatus& controller = _controllers[_currentIndex];

		if(controller.buttons.find(button) == controller.buttons.end())
			controller.buttons[button] = false;

		return controller.buttons[button];
	}

	bool XInputDH::getButtonPrev(KeyCode button)
	{
		DeviceStatus& controller = _controllers[_currentIndex];

		if(controller.prevButtons.find(button) == controller.prevButtons.end())
			controller.prevButtons[button] = false;

		return controller.prevButtons[button];
	}
}

		/*
	void XInputDH::genAnalogEvents()
	{
		XboxControllerData& controller = controllers[_currentIndex];
		XINPUT_STATE& state = controller.state;

		_leftThumbDir.set(state.Gamepad.sThumbLX, state.Gamepad.sThumbLY);
		_leftThumbMag = _leftThumbDir.length();
		_leftThumbDir.normalize();

		_rightThumbDir.set(state.Gamepad.sThumbRX, state.Gamepad.sThumbRY);
		_rightThumbMag = _rightThumbDir.length();
		_rightThumbDir.normalize();

		_leftTriggerMag = state.Gamepad.bLeftTrigger;
		_rightTriggerMag = state.Gamepad.bRightTrigger;



		// THUMB STICKS
		if(_leftThumbMag > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
			//make sure the magnitude isn't too large
			if(_leftThumbMag > 32767) _leftThumbMag = 32767;
			//adjust for deadzone
			_leftThumbMag -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
			//normalize magnitude
			_leftThumbMag /= (32767 - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);

			//send event
			//LOGINFO("XboxController #" + toString(controller.playerID, 0) + " left analog stick: " + toString(_leftThumbMag * _leftThumbDir.x, 0) + ", " + toString(_leftThumbMag * _leftThumbDir.y, 0) + ";");

			//since we sent a non zero event we have to make sure to later send a event with 0 values
			//TODO: do we really need this with the new input system?
			controller.needsLThumbReset = true;
		}
		else {
			if(controller.needsLThumbReset) {
				// send event with 0 mag
				controller.needsLThumbReset = false;
				//LOGINFO("XboxController #" + toString(controller.playerID, 0) + " left analog stick: 0.0, 0.0;");
			}
		}

		if(_rightThumbMag > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
			//make sure the magnitude isn't too large
			if(_rightThumbMag > 32767) _rightThumbMag = 32767;
			//adjust for deadzone
			_rightThumbMag -= XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
			//normalize magnitude
			_rightThumbMag /= (32767 - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);

			//send event
			LOGINFO("XboxController #" + toString(controller.playerID, 0) + " right analog stick: " + toString(_rightThumbMag * _rightThumbDir.x, 0) + ", " + toString(_rightThumbMag * _rightThumbDir.y, 0) + ";");

			//since we sent a non zero event we have to make sure to later send a event with 0 values
			//TODO: do we really need this with the new input system?
			controller.needsRThumbReset = true;
		}
		else {
			if(controller.needsRThumbReset) {
				// send event with 0 mag
				controller.needsRThumbReset = false;
				LOGINFO("XboxController #" + toString(controller.playerID, 0) + " right analog stick: 0.0, 0.0;");
			}
		}


		// TRIGGERS
		if(_leftTriggerMag > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) {
			//adjust for deadzone
			_leftTriggerMag -= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
			//normalize
			_leftTriggerMag /= (255 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD);

			//send event
			LOGINFO("XboxController #" + toString(controller.playerID, 0) + " left trigger: " + toString(_leftTriggerMag, 0) + ";");

			//since we sent a non zero event we have to make sure to later send a event with 0 values
			controller.needsLTriggerReset = true;
		}
		else {
			if(controller.needsLTriggerReset) {
				controller.needsLTriggerReset = false;
				LOGINFO("XboxController #" + toString(controller.playerID, 0) + " left trigger: " + toString(_leftTriggerMag, 0) + ";");
			}
		}

		if(_rightTriggerMag > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) {
			//adjust for deadzone
			_rightTriggerMag -= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
			//normalize
			_rightTriggerMag /= (255 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD);

			//send event
			LOGINFO("XboxController #" + toString(controller.playerID, 0) + " right trigger: " + toString(_rightTriggerMag, 0) + ";");

			//since we sent a non zero event we have to make sure to later send a event with 0 values
			controller.needsRTriggerReset = true;
		}
		else {
			if(controller.needsRTriggerReset) {
				controller.needsRTriggerReset = false;
				LOGINFO("XboxController #" + toString(controller.playerID, 0) + " right trigger: " + toString(_rightTriggerMag, 0) + ";");
			}
		}
	}

	void XInputDH::genDigitalEvents()
	{
		XboxControllerData& controller = controllers[_currentIndex];

		WORD prevButtons = controller.prevState.Gamepad.wButtons;
		WORD curButtons = controller.state.Gamepad.wButtons;
		WORD button;
		KeyCode kc;

		for(auto buttonPair : buttonMap)
		{
			button = buttonPair.first;
			kc = buttonPair.second;

			// TEMP TO GEN STRING OF ENUM
			String buttonName = "UNKNOWN";
			switch(kc) {
			case KC_Gamepad_DPadUp: buttonName = "Gamepad_DPadUp"; break;
			case KC_Gamepad_DPadDown: buttonName = "Gamepad_DPadDown"; break;
			case KC_Gamepad_DPadRight: buttonName = "Gamepad_DPadRight"; break;
			case KC_Gamepad_DPadLeft: buttonName = "Gamepad_DPadLeft"; break;
			case KC_Gamepad_SpecialRight: buttonName = "Gamepad_SpecialRight"; break;
			case KC_Gamepad_SpecialLeft: buttonName = "Gamepad_SpecialLeft"; break;
			case KC_Gamepad_LeftThumbstick: buttonName = "Gamepad_LeftThumbstick"; break;
			case KC_Gamepad_RightThumbstick: buttonName = "Gamepad_RightThumbstick"; break;
			case KC_Gamepad_LeftShoulder: buttonName = "Gamepad_LeftShoulder"; break;
			case KC_Gamepad_RightShoulder: buttonName = "Gamepad_RightShoulder"; break;
			case KC_Gamepad_FaceButtonBottom: buttonName = "Gamepad_FaceButtonBottom"; break;
			case KC_Gamepad_FaceButtonRight: buttonName = "Gamepad_FaceButtonRight"; break;
			case KC_Gamepad_FaceButtonLeft: buttonName = "Gamepad_FaceButtonLeft"; break;
			case KC_Gamepad_FaceButtonTop: buttonName = "Gamepad_FaceButtonTop"; break;
			}

			// generate press event
			if(!(prevButtons & button) && curButtons & button)
				LOGINFO("XboxController #" + toString(controller.playerID, 0) + ": " + buttonName + " pressed;");
			else if(prevButtons & button && !(curButtons & button))
				LOGINFO("XboxController #" + toString(controller.playerID, 0) + ": " + buttonName + " released;");
		}
	}

	void XInputDH::genEmulatedAnalogButtons()
	{
		XboxControllerData& controller = controllers[_currentIndex];

		static bool prevStatus = false;
		if(_leftThumbDir.x * _leftThumbMag < -XINPUT_VIRTUAL_STICK_BUTTON_THRESHOLD && !prevStatus) {
			prevStatus = true;
			LOGINFO("XboxController #" + toString(controller.playerID, 0) + ": LeftThumbLeft pressed;");
		}
		else if(_leftThumbDir.x * _leftThumbMag > -XINPUT_VIRTUAL_STICK_BUTTON_THRESHOLD && prevStatus)
		{
			LOGINFO("XboxController #" + toString(controller.playerID, 0) + ": LeftThumbLeft released;");
			prevStatus = false;
		}
	}*/




